<template>
  <div ref="graphContainer" class="graph-container"></div>
</template>

<script>
import * as d3 from 'd3';

export default {
  name: 'PartCategoryGraph',
  props: {
    graphData: {
      type: Object,
      required: true,
    },
  },
  data() {
    return {
      width: 1200,
      height: 900,
    };
  },
  mounted() {
    this.drawGraph();
  },
  watch: {
    graphData: {
      handler: 'drawGraph',
      deep: true, // 深度监听
    },
  },
  methods: {
    drawGraph() {
      // 清空现有的 SVG 元素
      d3.select(this.$refs.graphContainer).selectAll('*').remove();

      const svg = d3.select(this.$refs.graphContainer)
        .append('svg')
        .attr('width', this.width)
        .attr('height', this.height);

      // 定义箭头
      svg.append('defs').append('marker')
        .attr('id', 'arrowhead')
        .attr('viewBox', '-0 -5 10 10')
        .attr('refX', 25) // 箭头距离目标节点的距离
        .attr('refY', 0)
        .attr('orient', 'auto')
        .attr('markerWidth', 10)
        .attr('markerHeight', 10)
        .append('path')
        .attr('d', 'M0,-5 L10,0 L0,5')
        .attr('fill', '#999');

      // 处理节点数据
      const categories = this.graphData.categories.map(category => ({
        id: category.categoryId,
        name: category.categoryName,
        type: 'category', // 标记为零件分类节点
      }));

      const parts = this.graphData.parts.map(part => ({
        id: part.partNumber, // 使用 partNumber 作为 ID
        name: part.partName,
        type: 'part', // 标记为零件节点
        categoryId: part.categoryId, // 保留 categoryId
      }));

      const views = this.graphData.views.map(view => ({
        id: view.viewNumber, // 使用 viewNumber 作为 ID
        name: view.partNumber,
        type: 'view', // 标记为三视图节点
        viewType: view.viewType, // 视图类型
      }));

      const nodes = [...categories, ...parts, ...views];

      // 打印节点数据
      console.log('Nodes:', nodes);

      // 处理零件与零件分类之间的链接数据
      const partCategoryLinks = this.graphData.parts
        .filter(part => part.categoryId !== null) // 过滤掉没有分类的零件
        .map(part => {
          const source = nodes.find(node => node.id === part.partNumber && node.type === 'part'); // 零件节点
          const target = nodes.find(node => node.id === part.categoryId && node.type === 'category'); // 零件分类节点

          // 检查 source 和 target 是否存在
          if (!source || !target) {
            console.error('Invalid link:', { part, source, target });
            return null; // 返回 null，后续过滤掉无效链接
          }

          return { source, target };
        })
        .filter(link => link !== null); // 过滤掉无效链接

      // 处理零件与零件视图之间的链接数据
      const partViewLinks = this.graphData.views.map(view => {
        const source = nodes.find(node => node.id === view.partNumber && node.type === 'part'); // 零件节点
        const target = nodes.find(node => node.id === view.viewNumber && node.type === 'view'); // 零件视图节点

        // 检查 source 和 target 是否存在
        if (!source || !target) {
          console.error('Invalid link:', { view, source, target });
          return null; // 返回 null，后续过滤掉无效链接
        }

        return { source, target };
      }).filter(link => link !== null); // 过滤掉无效链接

      // 合并所有链接数据
      const links = [...partCategoryLinks, ...partViewLinks];

      // 打印链接数据
      console.log('Links:', links);

      // 创建力导向图模拟器
      const simulation = d3.forceSimulation(nodes)
        .force('link', d3.forceLink(links).id(d => d.id)) // 绑定链接数据
        .force('charge', d3.forceManyBody().strength(-100)) // 节点之间的排斥力
        .force('center', d3.forceCenter(this.width / 2, this.height / 2)) // 将节点居中
        .force('collision', d3.forceCollide().radius(30)) // 防止节点重叠
        .on('tick', () => this.ticked(link, node, label, viewTypeLabel, linkText));

      // 绘制链接
      const link = svg.append('g')
        .attr('class', 'link')
        .selectAll('line')
        .data(links)
        .enter()
        .append('line')
        .attr('stroke', '#999')
        .attr('stroke-width', 1.5)
        .attr('marker-end', 'url(#arrowhead)'); // 添加箭头

      // 在连线上标注文本
      const linkText = svg.append('g')
        .attr('class', 'link-text')
        .selectAll('text')
        .data(links)
        .enter()
        .append('text')
        .text(d => {
          // 如果是零件与零件视图之间的链接，标注 "HAS VIEW"
          if (d.target.type === 'view') return 'HAS VIEW';
          // 如果是零件与零件分类之间的链接，标注 "BELONG TO"
          if (d.target.type === 'category') return 'BELONG TO';
          return '';
        })
        .attr('font-size', 10) // 设置字体大小
        .attr('fill', '#333'); // 设置字体颜色

      // 绘制节点
      const node = svg.append('g')
        .attr('class', 'node')
        .selectAll('circle')
        .data(nodes)
        .enter()
        .append('circle')
        .attr('r', 20) // 将节点半径设置为 20
        .attr('fill', d => {
          if (d.type === 'category') return '#1f77b4'; // 零件分类节点为蓝色
          if (d.type === 'part') return '#ff7f0e'; // 零件节点为橙色
          if (d.type === 'view') return '#2ca02c'; // 三视图节点为绿色
        })
        .call(d3.drag()
          .on('start', this.dragstarted)
          .on('drag', this.dragged)
          .on('end', this.dragended)
        );

      // 添加节点名称
      const label = svg.append('g')
        .attr('class', 'label')
        .selectAll('text')
        .data(nodes)
        .enter()
        .append('text')
        .text(d => d.name) // 显示节点名称
        .attr('font-size', 12) // 设置字体大小
        .attr('dx', 25) // 水平偏移量，避免文字与节点重叠
        .attr('dy', 5); // 垂直偏移量，调整文字位置

      // 添加视图类型标注
      const viewTypeLabel = svg.append('g')
        .attr('class', 'viewTypeLabel')
        .selectAll('text')
        .data(nodes.filter(d => d.type === 'view')) // 只处理三视图节点
        .enter()
        .append('text')
        .text(d => {
          switch (d.viewType) {
            case '0': return '正视图';
            case '1': return '侧视图';
            case '2': return '俯视图';
            case '3': return '其他视图';
            default: return '未知视图';
          }
        })
        .attr('font-size', 10) // 设置字体大小
        .attr('dx', 25) // 水平偏移量，避免文字与节点重叠
        .attr('dy', 25); // 垂直偏移量，调整文字位置

      // 更新节点、链接和文字位置
      this.ticked(link, node, label, viewTypeLabel, linkText);
    },
    // 更新节点、链接和文字位置
    ticked(link, node, label, viewTypeLabel, linkText) {
      link
        .attr('x1', d => d.source?.x || 0) // 如果 source 不存在，默认值为 0
        .attr('y1', d => d.source?.y || 0) // 如果 source 不存在，默认值为 0
        .attr('x2', d => d.target?.x || 0) // 如果 target 不存在，默认值为 0
        .attr('y2', d => d.target?.y || 0); // 如果 target 不存在，默认值为 0

      node
        .attr('cx', d => d.x)
        .attr('cy', d => d.y);

      label
        .attr('x', d => d.x)
        .attr('y', d => d.y);

      // 更新视图类型标注的位置
      viewTypeLabel
        .attr('x', d => d.x)
        .attr('y', d => d.y);

      // 更新链接文本的位置
      linkText
        .attr('x', d => (d.source.x + d.target.x) / 2) // 文本居中
        .attr('y', d => (d.source.y + d.target.y) / 2);
    },
    // 拖动事件处理
    dragstarted(event, d) {
      if (!event.active) d3.forceSimulation().alphaTarget(0.3).restart();
      d.fx = d.x;
      d.fy = d.y;
    },
    dragged(event, d) {
      d.fx = Math.max(0, Math.min(this.width, event.x)); // 限制 x 范围
      d.fy = Math.max(0, Math.min(this.height, event.y)); // 限制 y 范围
    },
    dragended(event, d) {
      if (!event.active) d3.forceSimulation().alphaTarget(0);
      d.fx = null;
      d.fy = null;
    },
  },
};
</script>

<style scoped>
.graph-container {
  width: 100%;
  height: 100%;
}
</style>